// eee.Sheffield.PZ.Imaging
//
// Copyright ?Ping Zou, 2007
// sg71.cherub@gmail.com

namespace eee.Sheffield.PZ.Imaging
{
	using System;
	using System.IO;
	using System.Drawing;
	using System.Drawing.Imaging;
    using System.Collections.Generic;
    using eee.Sheffield.PZ.Math;
	using System.Collections;

	/// <summary>
	/// try consequtive scanline fitting
	/// </summary>
	public class TaskTryConsequtiveScanlineTracking
	{
        #region Fields
        private PZPoint _initCP;	// center point
        private PZDirection _initVD;		// vessel direction
        private double _initB;
        private int _initWidthOfScanline;

        private PZScanline scanline;		// scanline\        
        private int _d;		// forward step
        private string _exitMsg;	// exit message

        private List<TrackResult> _trackResultList;
        #endregion


        #region Properties
        public List<TrackResult> TrackResultList
        {
            get { return _trackResultList; }
            set { _trackResultList = value; }
        }
        #endregion


        public TaskTryConsequtiveScanlineTracking(PZPoint startPoint, PZDirection startDirection,
            int scanlineWidth, double background)
        {
            _initCP = new PZPoint(startPoint);
            _initVD = new PZDirection(startDirection);
            _initWidthOfScanline = scanlineWidth;
            _initB = background;

        }

		/// <summary>
		/// Apply Filter
		/// </summary>
		/// <param name="srcImg"></param>
		/// <returns></returns>
		public Bitmap Apply(Bitmap srcImg)
		{
			int width = srcImg.Width;
			int height = srcImg.Height;
			Bitmap dstImg = new Bitmap(srcImg);

			// -- prepare tracking
            //double startx = 330;    
            //double starty = 102;    
            //double startdx = 0;
            //double startdy = 1;
			//init_cp = new PZPoint(startx, starty);
			//init_vd = new PZDirection(startdx, startdy);
			_d = 1;

			// -- init current scanline
//			int init_widthofscanline = 22;
            //init_widthofscanline = 18;
			int init_s = 1;
			double init_c = _initWidthOfScanline / 2;
			double init_r = 4;
			double init_mu = 0.05;
			//double init_B = 25;


			// prepare candidates arrays
			List<PZPoint> cpCandidateList = new List<PZPoint>();
            //List<LineSegment> centreLineSegmentCandidateList = new List<LineSegment>();
			List<PZDirection> vdCandidateList = new List<PZDirection>();
			List<int> widthOfScanlineCandidateList = new List<int>();
			List<int> sampleRateCandidateList = new List<int>();
			List<double> cCandidateList = new List<double>();
			List<double> rCandidateList = new List<double>();
			List<double> muCandidateList = new List<double>();
            List<double> BCandidateList = new List<double>();
            //List<PZPoint> lastCPRecordList = new List<PZPoint>();


            cpCandidateList.Add(_initCP);
            vdCandidateList.Add(_initVD);
            widthOfScanlineCandidateList.Add(_initWidthOfScanline);
            sampleRateCandidateList.Add(init_s);
            cCandidateList.Add(init_c);
            rCandidateList.Add(init_r);
            muCandidateList.Add(init_mu);
            BCandidateList.Add(_initB);

			// prepare last arrays
            List<PZPoint> cp_last = new List<PZPoint>();
            List<PZDirection> vd_last = new List<PZDirection>();
            List<double> mean_r_last = new List<double>();
            List<double> mean_mu_last = new List<double>();
            List<double> mean_B_last = new List<double>();

            //List<TrackResult> tr_results = new List<TrackResult>();
            _trackResultList = new List<TrackResult>();

            List<int> rootIndexList = new List<int>();
            List<int> currentIndexList = new List<int>();
            rootIndexList.Add(-1);

            int layer = 0;
            int index = 0;

			// do scanline tracking
            try
            {
                while (cpCandidateList.Count > 0)
				{                                        
                    cp_last.Clear();
					vd_last.Clear();
					mean_r_last.Clear();
					mean_mu_last.Clear();
					mean_B_last.Clear();                    

                    // 2. track from each cp candidate
                    int cpCandidateCount = cpCandidateList.Count;
                    currentIndexList.Clear();
                    for (int i = 0; i < cpCandidateCount; i++)
						// for each cp candidate
					{
                        // layer = 0 for the root
                        // index start from 1
                        // root's root index = -1;
                        index++;
                        TrackResult tr = new TrackResult(layer);
                        tr._index = index;
                        currentIndexList.Add(index);
                        tr._rootIndexList.Add(rootIndexList[i]);
                        //tr._rootIndex = rootIndexList[i];
                        

                        System.Console.WriteLine("Tracking {0} of {1} candidates", i + 1, cpCandidateList.Count);

                        PZPoint cp_current = cpCandidateList[i];
                        PZDirection vd_current = vdCandidateList[i];

						System.Console.Write("Start from " + cp_current.ToString());
						System.Console.Write(" with direction of " + vd_current.ToString());
                        System.Console.Write(" width of scanline " + widthOfScanlineCandidateList[i]);
                        System.Console.Write(" init c = " + cCandidateList[i]);
                        System.Console.Write(" init r = " + rCandidateList[i]);
                        System.Console.Write(" init mu = " + muCandidateList[i]);
                        System.Console.WriteLine(" init B = " + BCandidateList[i]);

						int widthofscanline = widthOfScanlineCandidateList[i];
						int s = sampleRateCandidateList[i];
						double c = cCandidateList[i];
						double r = rCandidateList[i];
						double mu = muCandidateList[i];
						double B = BCandidateList[i];
						// prepare scanline
						scanline = new PZScanline(widthofscanline, s, c, r, mu, B);
						ConsequtiveScanlineTracking cs = 
							new ConsequtiveScanlineTracking(cp_current, vd_current, scanline, _d);
						
						int count = 0;
						bool exit = false;
                        bool distanceIsTooLarge = false;

                        int widthM1 = dstImg.Width - 1;
                        int heightM1 = dstImg.Height - 1;

						while (!exit)
						{
							count ++;

							System.Console.Write(count + ": ");
							string filename = "scanline_record.txt";
							FileStream fs = new FileStream(filename, FileMode.Append, FileAccess.Write);
							StreamWriter w = new StreamWriter(fs);
							w.WriteLine("******** " + (count + 1) + " ********");
							w.Close();
							fs.Close();

							cs.Apply(srcImg);
							cs.lastsl = cs.currentsl;
							cs.currentsl = new PZScanline(cs.lastsl._widthOfScanline, cs.lastsl._sampleRate,
								cs.lastsl._c, cs.lastsl._r, cs.lastsl._mu, cs.lastsl._B);
							
							System.Console.WriteLine(String.Format(
								"{0,-15}{1, -15}{2, -8:f}{3, -8:f}{4, -8:f}{5, -8:f}",
								cs.cp.ToString(), cs.vd.ToString(), 
								cs.lastsl._c, cs.lastsl._r, cs.lastsl._mu, cs.lastsl._B));

                            // calculate distance from the last CP
                            distanceIsTooLarge = false;
                            //if (tr.cpArray.Count > 0)
                            //{
                            //    PZPoint currentCP = cs.cp;
                            //    PZPoint lastCP = tr.cpArray[tr.cpArray.Count - 1];
                            //    double dist = currentCP.Distance(lastCP);
                            //    if (dist > 2 * _d)
                            //        distanceIsTooLarge = true;
                            //}

                            if (cs.cp.x >= 0 && cs.cp.x < widthM1
                                && cs.cp.y >= 0 && cs.cp.y < heightM1
                                && cs.ep1.x >= 0 && cs.ep1.x < widthM1
                                && cs.ep1.y >= 0 && cs.ep1.y < heightM1
                                && cs.ep2.x >= 0 && cs.ep2.x < widthM1
                                && cs.ep2.y >= 0 && cs.ep2.y < heightM1
                                && !distanceIsTooLarge)
                            {
                                // collect tracking result
                                PZPoint cp_add = new PZPoint(cs.cp);
                                PZPoint ep1_add = new PZPoint(cs.ep1);
                                PZPoint ep2_add = new PZPoint(cs.ep2);
                                PZDirection vd_add = new PZDirection(cs.vd);
                                tr.Add(cp_add, ep1_add, ep2_add, cs.lastsl, vd_add);

                                // run estimates criteria
                                exit = tr.Exit();
                                _exitMsg = "Exit by tr: " + tr._exitMsg;

                                // lack of fit criteria
                                if (!exit)
                                {
                                    exit = !cs.isfit;
                                    _exitMsg = "Exit by scanline: " + cs.exit_msg;
                                }

                                // track back criteria
                                if (!exit)
                                {
                                    foreach (TrackResult tempTR in _trackResultList)
                                    {
                                        if (tempTR.Contains(cs.cp))
                                        {
                                            exit = true;
                                            _exitMsg = "tracking back";
                                            break;
                                        }
                                    }
                                }

                                if (exit)
                                    System.Console.WriteLine(_exitMsg);

                                dstImg.SetPixel((int)System.Math.Round(cs.cp.x), (int)System.Math.Round(cs.cp.y), Color.Red);
                                //dstImg.SetPixel((int)cs.ep1.x, (int)cs.ep1.y, Color.Green);
                                //dstImg.SetPixel((int)cs.ep2.x, (int)cs.ep2.y, Color.Blue);
                                // output intermedia tracking result (image)
                                string dstImageFileName = "intermedia image.bmp";// +count + ".bmp";
                                dstImg.Save(dstImageFileName, ImageFormat.Bmp);
                            }
                            else
                            {
                                if (distanceIsTooLarge)
                                {
                                    _exitMsg = "distance too large";
                                }
                                else
                                {
                                    
                                    _exitMsg = "Track to image edge";
                                }
                                exit = true;
                                System.Console.WriteLine(_exitMsg);
                            }
						} // while

						// add to last arrays
                        if (tr._cpArray.Count >= 2 && _exitMsg != "tracking back")
						{
							cp_last.Add(tr._cpArray[tr._count - 2]);
							vd_last.Add(tr._vdArray[tr._count - 2]);
							mean_r_last.Add(tr._mean_r);
							mean_mu_last.Add(tr._mean_mu);
							mean_B_last.Add(tr._mean_B);
						}

                        _trackResultList.Add(tr);
					} // for
					
					System.Console.WriteLine("All {0} candidates tracking stopped", cpCandidateList.Count);

                    //centreLineSegmentCandidateList.Clear();
                    //lastCPRecordList.Clear();
					cpCandidateList.Clear();
					vdCandidateList.Clear();
					widthOfScanlineCandidateList.Clear();
					sampleRateCandidateList.Clear();
					cCandidateList.Clear();
					rCandidateList.Clear();
					muCandidateList.Clear();
					BCandidateList.Clear();

                    layer++;
                    rootIndexList.Clear();


                    // Look Ahead Detect for new cp/centre line segment candidates
                    int cpSearchCount = cp_last.Count;
                    for (int i = 0; i < cpSearchCount; i ++)
                    {
                        PZPoint cpSearch = cp_last[i];
                        PZDirection vdSearch = vd_last[i];
                        double rSearch = mean_r_last[i];
                        int backgroundSearch = (int)mean_B_last[i];
                        System.Console.WriteLine("Searching {0} of {1}, from {2} with direction {3}",
							i + 1, cp_last.Count, cpSearch.ToString(), vdSearch.ToString());

                        // Look Ahead Detection
                        LookAheadDetection lad = new LookAheadDetection(cpSearch, vdSearch, rSearch, backgroundSearch, _trackResultList);
                        lad.Apply(srcImg);
                        int ladReturnCount = lad._cpCandidateList.Count;
                        System.Console.WriteLine("return {0} candidates", ladReturnCount);
                        lad.DrawCentreLineSegmentCandidate(srcImg, "centre line candidate.bmp");

                        // add to candidate lists
                        int minWidthOfScanline = 10;
                        for (int j = 0; j < ladReturnCount; j++)
                        {
                            cpCandidateList.Add(lad._cpCandidateList[j]);
                            vdCandidateList.Add(lad._vdCandidateList[j]);
                            double rCandidate = lad._rCandidateList[j];
                            rCandidateList.Add(rCandidate);
                            int widthOfScanLineCandidate = rCandidate * 3 > minWidthOfScanline ? (int)(rCandidate * 3) : minWidthOfScanline;

                            //centreLineSegmentCandidateList.Add(lad._candidateCentreLineSegmentList[j]);
                            //vdCandidateList.Add(lad._candidateDirectionList[j]);
                            //lastCPRecordList.Add(lad._lastCPRecordList[j]);
                            //int widthOfScanLineCandidate = rSearch * 3 > 8 ? (int)(rSearch * 3) : 8;

                            widthOfScanlineCandidateList.Add(widthOfScanLineCandidate);
                            sampleRateCandidateList.Add(1);
                            cCandidateList.Add((double)widthOfScanLineCandidate / 2);
                            //rCandidateList.Add(rSearch);
                            muCandidateList.Add(mean_mu_last[i]);
                            BCandidateList.Add(mean_B_last[i]);

                            rootIndexList.Add(currentIndexList[i]);

                            // add Look Ahead Detection track history
                            //cpSearch = new PZPoint(cp_last[i]);
                            //TrackResult tr = new TrackResult(layer);
                            //PZPoint endPoint = lad._cpCandidateList[j];
                            //PZDirection vdAdd = new PZDirection(cpSearch, endPoint);
                            //PZDirection sd = new PZDirection(vdAdd);
                            //sd.Rotate(90);
                            //while (cpSearch.Distance(endPoint) > 1)
                            //{                                
                            //    PZPoint cpAdd = new PZPoint(cpSearch);
                            //    PZPoint ep1Add = new PZPoint();
                            //    ep1Add.ExtendFrom(cpAdd, sd, rCandidate);
                            //    PZPoint ep2Add = new PZPoint();
                            //    ep2Add.ExtendFrom(cpAdd, sd, -1.0 * rCandidate);
                            //    tr.Add(cpAdd, ep1Add, ep2Add, vdAdd, rCandidate, mean_mu_last[i], mean_B_last[i]);
                            //    cpSearch.ExtendFrom(cpSearch, vdAdd, 1.0);
                            //}
                            //PZPoint ep1EndAdd = new PZPoint();
                            //ep1EndAdd.ExtendFrom(endPoint, sd, rCandidate);
                            //PZPoint ep2EndAdd = new PZPoint();
                            //ep2EndAdd.ExtendFrom(endPoint, sd, -1.0 * rCandidate);
                            //tr.Add(endPoint, ep1EndAdd, ep2EndAdd, vdAdd, rCandidate, mean_mu_last[i], mean_B_last[i]);
                            //tr_results.Add(tr);
                        }                        
                    }

                    //// search for new candidates
                    //for (int i = 0; i < cp_last.Count; i ++)
                    //    // for each last cp
                    //{
                    //    PZPoint cp_search = new PZPoint(cp_last[i] as PZPoint);
                    //    PZDirection vd_search = new PZDirection(vd_last[i] as PZDirection);

                    //    System.Console.WriteLine("Searching {0} of {1}, from {2} with direction {3}",
                    //        i + 1, cp_last.Count, cp_search.ToString(), vd_search.ToString());
						
                    //    double r_search = (double) mean_r_last[i];


                    //    LookForwardSearch search = new LookForwardSearch(cp_search, vd_search, r_search);
                    //    search.Apply(srcImg);
                    //    System.Console.WriteLine("return {0} candidates", search._cpCandidates.Count);


                    //    for (int j = 0; j < search._cpCandidates.Count; j ++)
                    //        // for each cp candidates
                    //    {
                    //        // check for cp candidate, no return in searched vessel
                    //        bool add = true;							
                    //        for (int k = 0; k < tr_results.Count; k ++)
                    //        {
                    //            double last_mean_r = (tr_results[k] as TrackResult).mean_r;
                    //            PZPoint temp_candidate = new PZPoint(search._cpCandidates[j] as PZPoint);
                    //            for (int l = 0; l < (tr_results[k] as TrackResult).count; l ++)
                    //            {
                    //                PZPoint temp_cp = new PZPoint((tr_results[k] as TrackResult).cpArray[l] as PZPoint);
                    //                if (temp_cp.Distance(temp_candidate) <= last_mean_r)
                    //                    add = false;
                    //            }
                    //        }
                    //        if (add)
                    //        {	
                    //            System.Console.WriteLine("{0} of {1} candidate, {2} with direction {3}, was added",
                    //                j + 1, search._cpCandidates.Count,
                    //                (search._cpCandidates[j] as PZPoint).ToString(),
                    //                (search._vdCandidates[j] as PZDirection).ToString());

                    //            cpCandidateList.Add(search._cpCandidates[j] as PZPoint);
                    //            vdCandidateList.Add(search._vdCandidates[j] as PZDirection);
                    //            int widthofscanline_candidate = (int) ((double) mean_r_last[i] * 3);
                    //            if (widthofscanline_candidate < 12)
                    //                widthofscanline_candidate = 12;
                    //            //if (widthofscanline_candidate > 16)
                    //            //	widthofscanline_candidate = 16;
                    //            widthOfScanlineCandidateList.Add(widthofscanline_candidate);
                    //            sampleRateCandidateList.Add(1);
                    //            cCandidateList.Add((double) widthofscanline_candidate / 2);
                    //            rCandidateList.Add((double) mean_r_last[i]);
                    //            muCandidateList.Add((double) mean_mu_last[i]);
                    //            BCandidateList.Add((double) mean_B_last[i]);
                    //        }
                    //    }
                    //}	

				} // while

            } // try
            catch (ApplicationException e)
            {
                System.Console.WriteLine("exit consequtive scanline fitting because: " + e.Message);
            }
            finally
            {
                string trackResultFilename;
                for (int i = 0; i < _trackResultList.Count; i++)
                {
                    if (_trackResultList[i].Check(srcImg.Width, srcImg.Height))
                    {
                        trackResultFilename = "Trackresult_" + i.ToString() + ".txt";
                        (_trackResultList[i] as TrackResult).Write(trackResultFilename);
                    }
                }
                System.Console.WriteLine("continue the bifurcation search");
            }

             //-- return dstImg
			return dstImg;
		}

        /// <summary>
        /// centre line segment candidate to centre point candidate
        /// compare points on centre line segment candidate with the tracking history
        /// </summary>
        /// <param name="centreLineSegmentCandidateList"></param>
        /// <param name="tr_results"></param>
        /// <param name="cpCandidateList"></param>
        private void CentreLineSegmentCandidateToCentrePointCandidate(
            List<LineSegment> centreLineSegmentCandidateList,List<PZPoint> lastCPRecordList,
            List<TrackResult> tr_results,
            ref List<PZPoint> cpCandidateList, ref List<PZDirection> vdCandidateList)
        {
            List<PZDirection> newVDCandidateList = new List<PZDirection>();
            cpCandidateList.Clear();

            int clscCount = centreLineSegmentCandidateList.Count;
            for (int i = 0; i < clscCount; i++)
            {
                LineSegment l = centreLineSegmentCandidateList[i];
                PZDirection vd = vdCandidateList[i];
                PZPoint lastCP = lastCPRecordList[i];
                // for each centre line candidate
                List<PZPoint> tempCPList = new List<PZPoint>(l.PointList.Count);
          
                // search centre line candidate with the tracking history
                bool isInTrackHistory;
                foreach (PZPoint p in l.PointList)
                // for each point on the centre line cendiate
                {
                    isInTrackHistory = false;
                    foreach (TrackResult tr in tr_results)
                    {
                        int trRecordCount = tr._cpArray.Count - 2;
                        for (int j = 0; j < trRecordCount; j++)
                        {
                            PZPoint cp = tr._cpArray[j];
                            double r = tr._rArray[j];
                            if (p.Distance(cp) < r)
                            {
                                isInTrackHistory = true;
                                break;
                            }                            
                        }
                        if (isInTrackHistory)
                            break;
                    }
                    if (!isInTrackHistory)
                        tempCPList.Add(p);
                }

                // find valid centre line candidate
                int tempCPListCount = tempCPList.Count;
                if (tempCPListCount > 2)
                {
                    int midIndex = tempCPListCount / 2;
                    // centre point candidate
                    PZPoint cpCandidate = new PZPoint(tempCPList[midIndex]);
                    cpCandidateList.Add(cpCandidate);
                    // vd candidate
                    double xDiff = (tempCPList[midIndex + 1].x - tempCPList[midIndex - 1].x) / 2.0;
                    double yDiff = (tempCPList[midIndex + 1].y - tempCPList[midIndex - 1].y) / 2.0;
                    PZPoint temp = new PZPoint(cpCandidate.x + xDiff, cpCandidate.y + yDiff);
                    // new centre line should "lead away" the last centre point
                    if (temp.Distance(lastCP) < cpCandidate.Distance(lastCP))
                    {
                        newVDCandidateList.Add(new PZDirection(-xDiff, -yDiff));
                    }
                    else
                    {
                        newVDCandidateList.Add(new PZDirection(xDiff, yDiff));
                    }
                }
            }
            vdCandidateList = newVDCandidateList;
        } // CentreLineSegmentCandidateToCentrePointCandidate()
	}
}
